package in.co.codedoc.idcontainer;

import in.co.codedoc.cg.annotations.DBType;
import in.co.codedoc.encrypt.HashValue;
import in.co.codedoc.ioc.IOCContainer;
import in.co.codedoc.sql.DBMapUtil;
import in.co.codedoc.sql.DBMappedEntity;
import in.co.codedoc.sql.DBOutputColumnMap;
import in.co.codedoc.sql.IDBase;
import in.co.codedoc.sql.InsertStatement;
import in.co.codedoc.sql.QueryStatement;
import in.co.codedoc.sql.SequenceGenerator;
import in.co.codedoc.sql.SessionLocal;
import in.co.codedoc.sql.Table;
import in.co.codedoc.sql.TableColumn;
import in.co.codedoc.sql.mapping.BigintLongOutputColumnMap;
import in.co.codedoc.sql.mapping.CharStringOutputColumnMap;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;

public class IdContainer
	implements DBMappedEntity
{
	public void SetId(IdContainerId idContainerId)
	{
		id = idContainerId;
	}

	public IdContainerId GetId()
	{
		return id;
	}
	
	public void Reload()
	{
		try
		{
			members = null;
			
			QueryStatement query = new QueryStatement();
			
			query.Select(IdContainerMemberColumns.MemberId)
				.Where().And(IdContainerMemberColumns.ContainerId, id);
			
			query.OrderBy().Ascending(IdContainerMemberColumns.Location);
			
			query.Execute();
			
			ArrayList<Long> temp = new ArrayList<Long>();
			while(query.FetchNextRow())
			{
				temp.add
				(
					(
						(BigintLongOutputColumnMap)query.GetColumnMap
						(
							IdContainerMemberColumns.MemberId
						)
					).GetValue()
				);
			}
			
			members = new long[temp.size()];
			int memberIndex = 0;
			for(Long memberId:temp)
			{
				members[memberIndex++] = memberId;
			}
		}
		catch(SQLException ex)
		{
			throw new RuntimeException
			(
				"Exception while reloading container with " + id + ":" + ex.getMessage(),
				ex
			);
		}
	}


	public void Copy(Collection<Long> container)
	{
		for(long member:members)
		{
			container.add(member);
		}
	}
	
	public static IdContainer BindOrSave(Collection<Long> members)
	{
		return BindOrSave(members, 0);
	}
	
	private static IdContainer BindOrSave(Collection<Long> members,int attempt)
	{
		HashValue digest = new HashValue(members);
		
		try
		{
			QueryStatement query = new QueryStatement();
			
			query.Select(IdContainerColumns.Id)
				.Where()
				.And
				(
					IdContainerColumns.Digest,
					digest,
					DBType.CHAR
				);
			
			query.Execute();
			
			if(query.FetchNextRow())
			{
				return 
					IdContainerId.Create
					(
						((BigintLongOutputColumnMap)query.GetColumnMap(IdContainerColumns.Id))
						.GetValue()
					).Get();
			}
			else
			{
				InsertStatement insert = new InsertStatement();
				
				long conatinerId 
					= IOCContainer.Lookup(SequenceGenerator.class).GetNextValue("IdContainerId");
				insert
					.Set
					(
						IdContainerColumns.Id,
						conatinerId
					)
					.Set
					(
						IdContainerColumns.Digest,
						digest,
						DBType.CHAR
					);
				
				if(!insert.ExecuteUpdate())
				{
					//Try again - seems have appeared
					if(attempt < 5)
					{
						return BindOrSave(members,++attempt);
					}
					else
					{
						throw new RuntimeException
						(
							"Failed to create id container entry with digest " + digest
						);
					}
				}
				
				//Insert member entries
				InsertStatement insert2 = new InsertStatement();
				insert2
					.Set(IdContainerMemberColumns.ContainerId, conatinerId)
					.Set(IdContainerMemberColumns.MemberId)
					.Set(IdContainerMemberColumns.Location);
			
				short location = 0;

				for(long memberId:members)
				{
					insert2.Bind(IdContainerMemberColumns.MemberId,memberId);
					insert2.Bind(IdContainerMemberColumns.Location,location);
					if(!insert2.ExecuteUpdate())
					{
						throw new RuntimeException
						(
							"Unexpected condition. Failed to insert" + 
							"(ContainerId:" + conatinerId + "," +
							"MemberId:" + memberId + "," +
							"Location:" + location + ") into IdContainerMember table."
						);
					}
					++location;
				}
				
				return IdContainerId.Create(conatinerId).Get();
			}
		}
		catch(Throwable th)
		{
			throw new RuntimeException
			(
				"Failed to BindOrSave members with digest " + digest + ":" + th.getMessage(),
				th
			);
		}
	}
	
	private IdContainerId id;
	private long[] members;
	
	public static class IdContainerId
		extends IDBase
	{
		private IdContainerId(long rawId)
		{
			super(rawId);
		}
		
		public IdContainer Get()
		{
			IdContainer entity = cachedEntityHolder.get();
			
			if(entity == null)
			{
				cachedEntityHolder.set(entity = new IdContainer());
				entity.SetId(this);
				entity.Reload();
			}
			
			return entity;
		}
		
		public static IdContainerId Create(long rawId)
		{
			if(rawId == Long.MIN_VALUE)
			{
				return null;
			}
			
			return (IdContainerId)(new IdContainerId(rawId).Intern());
		}
		
		private Object readResolve() 
			throws java.io.ObjectStreamException
		{
			return Create(GetId());
		}
		
		private SessionLocal<IdContainer> cachedEntityHolder
			= new SessionLocal<IdContainer>();
	}

	public static enum IdContainerColumns
		implements TableColumn
	{
		Id,Digest;
		
		@Override
		public PrintWriter Generate(PrintWriter out) throws IOException
		{
			out.print(GetName());
			return out;
		}
	
		public static final Table TABLE = new Table("IdContainer");
		
		@Override
		public Table GetTable()
		{
			return TABLE;
		}
		@Override
		public DBOutputColumnMap GetDBOutputColumnMap()
		{
			switch(this)
			{
				case Id:
					return BigintLongOutputColumnMap.GetDBOutputColumnMap();
	 			case Digest:
					return CharStringOutputColumnMap.GetDBOutputColumnMap();
				default:
					throw new RuntimeException("Impossible! Non existent enum value! Perhaps some build/classpath glitch");
			}
		}
	
		@Override
		public String GetName()
		{
			switch(this)
			{
				case Id:
					return "id";
	 			case Digest:
					return "digest";
				default:
					throw new RuntimeException("Impossible! Non existent enum value! Perhaps some build/classpath glitch");
			}
		}
	
		@Override
		public String GetQualifiedName()
		{
			return GetName();
		}
	}

	public static enum IdContainerMemberColumns
		implements TableColumn
	{
		ContainerId,MemberId,Location;
		
		@Override
		public PrintWriter Generate(PrintWriter out) throws IOException
		{
			out.print(GetName());
			return out;
		}
	
		public static final Table TABLE = new Table("IdContainerMember");
		
		@Override
		public Table GetTable()
		{
			return TABLE;
		}
		@Override
		public DBOutputColumnMap GetDBOutputColumnMap()
		{
			switch(this)
			{
				case ContainerId:
					return IOCContainer.Lookup(DBMapUtil.class).GetOutputColumnMap(DBType.BIGINT,Long.TYPE);
	 			case MemberId:
					return IOCContainer.Lookup(DBMapUtil.class).GetOutputColumnMap(DBType.BIGINT,Long.TYPE);
	 			case Location:
					return IOCContainer.Lookup(DBMapUtil.class).GetOutputColumnMap(DBType.SMALLINT,Short.TYPE);
				default:
					throw new RuntimeException("Impossible! Non existent enum value! Perhaps some build/classpath glitch");
			}
		}
	
		@Override
		public String GetName()
		{
			switch(this)
			{
				case ContainerId:
					return "containerId";
	 			case MemberId:
					return "memberId";
	 			case Location:
					return "location";
				default:
					throw new RuntimeException("Impossible! Non existent enum value! Perhaps some build/classpath glitch");
			}
		}
	
		@Override
		public String GetQualifiedName()
		{
			return GetName();
		}
	}
}
